{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Estimators 概览\n",
    "这篇文章介绍了 [tf.estimator](https://tensorflow.google.cn/api_docs/python/tf/estimator) —— tensorflow 的一个高级 API。Estimators 封装了以下方法：\n",
    "1. 训练\n",
    "2. 评估\n",
    "3. 预测\n",
    "4. 暴露的接口\n",
    "你可以使用 tensorflow 提供的预估程序，也可以编写自己的自定义预估程序。所有的评估器——不管是预做的还是自定义的——都是基于 [tf.estimator.Estimator](https://tensorflow.google.cn/api_docs/python/tf/estimator/Estimator) 类的类。\n",
    "\n",
    "想进去快速示例，请尝试 [estimator tutorials](https://tensorflow.google.cn/tutorials/estimator/linear)。有关API设计的概述，请参阅 [white paper](https://arxiv.org/abs/1708.02637)。\n",
    "\n",
    "## 优势\n",
    "与 tf.keras.Model 类似，Estimator 是模型级抽象。tf.estimator 为 tf.keras 提供了一些目前仍在开发中的功能：\n",
    "1. 参数基于训练的服务\n",
    "2. 全 [TFX](http://tensorflow.org/tfx) 集成\n",
    "\n",
    "## Estimator 能力\n",
    "Estimator 提供了以下好处：\n",
    "1. 你可以在本地主机或分布式多服务器环境上运行基于 Estimator 的模型，而无需更改你的模型。此外，你可以在 CPU、GPU 或 TPU 上运行基于 Estimator 的模型，而无需重新编写模型。\n",
    "2. Estimator 提供一个安全的分布式训练循环，控制如何和何时：\n",
    "    1. 加载数据\n",
    "    2. 处理错误\n",
    "    3. 创建检查点文件和从错误中恢复\n",
    "    4. 使用 tensorboard 保存模型概述\n",
    "在编写带有 Estimator 的应用程序时，必须将数据输入管道与模型分开。这种分离简化了不同数据集的实验。\n",
    "\n",
    "## 预创建 Estimator \n",
    "预创建 Estimator 使你能够在比基本 tensorflow API 更高的概念级别上工作。你不再需要操心创建计算图或会话，因为 Estimator 为你处理所有的  \"管道\"。此外，预创建的 Estimator 允许你通过只做最少的代码更改来尝试不同的模型架构。例如，DNNClassifier 是一个预先生成的估计类，它训练基于密集的前馈神经网络的分类模型。\n",
    "\n",
    "### 预创建 Estimator 的结构\n",
    "一个基于预创建的 tensorflow 程序通常包括以下四个步骤:\n",
    "#### 编写一个或多个数据集导入函数\n",
    "例如，你可以创建一个函数来导入训练集，另一个函数来导入测试集。每个数据集导入函数必须返回两个对象：\n",
    "- 一种键是特征名，值是张量（或稀疏张量），包含相应的特征数据的字典。\n",
    "- 一个包含一个或多个标签的张量。\n",
    "比如，下面的代码演示了输入函数的基本框架：\n",
    "```python\n",
    "def input_fn(dataset):\n",
    "    ...  # 操作数据集，提取特征字典和标签\n",
    "    return feature_dict, label\n",
    "```\n",
    "想要获取更详细的信息可查看 [data guide](https://tensorflow.google.cn/guide/data)。\n",
    "\n",
    "#### 定义特征列\n",
    "每个 [tf.feature_column](https://tensorflow.google.cn/api_docs/python/tf/feature_column) 标识一个功能名称、它的类型和输入预处理。例如，下面的代码片段创建了三个包含整数或浮点数据的特征列。前两个特征列简单地标识了特性的名称和类型。第三个特征列还指定了一个lambda 程序来调用它来缩放原始数据：\n",
    "```python\n",
    "# 定义三个数值的特征列\n",
    "population = tf.feature_column.numeric_column('population')\n",
    "crime_rate = tf.feature_column.numeric_column('crime_rate')\n",
    "median_education = tf.feature_column.numeric_column(\n",
    "  'median_education',\n",
    "  normalizer_fn=lambda x: x - global_education_mean)\n",
    "```\n",
    "想要获取更多的信息，请参考 [feature columns  tutorial](https://tensorflow.google.cn/tutorials/keras/feature_columns)。\n",
    "\n",
    "#### 实例化相关的预创建 Estimator\n",
    "比如，这里有一个实例化名字为 LinearClassifier 的预创建 Estimator 的例子：\n",
    "```python\n",
    "# 实例化一个 Estimator，传入特征列\n",
    "estimator = tf.estimator.LinearClassifier(\n",
    "  feature_columns=[population, crime_rate, median_education])\n",
    "```\n",
    "为了获取更多的信息，请参考 [linear classifier tutorial](https://tensorflow.google.cn/tutorials/estimator/linear)。\n",
    "\n",
    "#### 调用训练、评估或者推断方法\n",
    "比如，所有的 Estimator 都提供了一个 train 方法来训练模型：\n",
    "```python\n",
    "# input_fn 是在步骤1中创建的函数\n",
    "estimator.train(input_fn=my_training_set, steps=2000)\n",
    "```\n",
    "你可以在下面看到一个这样的例子。\n",
    "\n",
    "### 预创建的好处\n",
    "预创建提供以下好处：\n",
    "1. 确定计算图的不同部分应在何处运行的最佳实践，在单个计算机或集群上实现策略。\n",
    "2. 事件（摘要）编写和通用摘要的最佳实践。\n",
    "如果不使用预先生成的估计器，则必须自己实现前面的功能。\n",
    "\n",
    "## 自定义 Estimator\n",
    "每一个 Estimator 的核心——无论是预创建的还是定制的——都是它的模型函数，它是一种为训练、评估和预测构建图形的方法。当你使用预创建的 Estimator 时，其他人也已经实现了模型函数。在依赖自定义 Estimator 时，你必须自己编写模型函数。\n",
    "\n",
    "## 推荐的工作流程\n",
    "1. 假设存在一个适当的预创建 estimator，使用它来构建你的第一个模型，并使用它的结果来建立一个基线。\n",
    "2. 建立和测试你的整个管道，包括数据的完整性和可靠性。\n",
    "3. 如果有合适的预创建 Estimator 可用，运行实验来确定哪个预创建 Estimator 会产生最佳结果。\n",
    "4. 可能，通过构建自己的 Estimator 进一步改进模型。\n",
    "\n",
    "## 从 Keras 模型中创建 Estimator\n",
    "你可以使用 tf.keras.estimator.model_to_estimator 将现有的 Keras 模型转换为 Estimator。这样做可以使你的 Keras 模型具有 Estimator 的优势，例如分布式训练。\n",
    "\n",
    "实例化 Keras MobileNet V2 模型，并使用优化器、损耗和要培训的指标来编译模型:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from tensorflow import tf\n",
    "\n",
    "keras_mobilenet_v2 = tf.keras.applications.MobileNetV2(\n",
    "    input_shape=(160, 160, 3), include_top=False)\n",
    "keras_mobilenet_v2.trainable = False\n",
    "\n",
    "estimator_model = tf.keras.Sequential([\n",
    "    keras_mobilenet_v2,\n",
    "    tf.keras.layers.GlobalAveragePooling2D(),\n",
    "    tf.keras.layers.Dense(1)\n",
    "])\n",
    "\n",
    "# 编译模型\n",
    "estimator_model.compile(\n",
    "    optimizer='adam',\n",
    "    loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
    "    metrics=['accuracy'])\n",
    "\n",
    "# 从编译的 Keras 模型创建一个 Estimator。Keras 模型的初始模型状态保存在创建的 Estimator中:\n",
    "est_mobilenet_v2 = tf.keras.estimator.model_to_estimator(keras_model=estimator_model)\n",
    "\n",
    "# 对待派生的  就像对待任何其它 Estimator 一样。\n",
    "IMG_SIZE = 160  # 所有图片被重新转换为160x160\n",
    "\n",
    "def preprocess(image, label):\n",
    "  image = tf.cast(image, tf.float32)\n",
    "  image = (image/127.5) - 1\n",
    "  image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE))\n",
    "  return image, label\n",
    "\n",
    "def train_input_fn(batch_size):\n",
    "  data = tfds.load('cats_vs_dogs', as_supervised=True)\n",
    "  train_data = data['train']\n",
    "  train_data = train_data.map(preprocess).shuffle(500).batch(batch_size)\n",
    "  return train_data\n",
    "\n",
    "# 调用 Estimator 的 train 函数进行训练\n",
    "est_mobilenet_v2.train(input_fn=lambda: train_input_fn(32), steps=500)\n",
    "\n",
    "# 调用 Estimator 的 evaluate 函数进行评估\n",
    "est_mobilenet_v2.evaluate(input_fn=lambda: train_input_fn(32), steps=10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了获取更多信息，请参阅 [tf.keras.estimator.model_to_estimator](https://tensorflow.google.cn/api_docs/python/tf/keras/estimator/model_to_estimator)。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6rc1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
